<template>
  <el-dialog
    title="图片上传"
    :visible.sync="uploadVisible"
    width="950px"
    append-to-body
    @close="closeDialog"
  >
    <el-upload
      ref="uploadImage"
      action="#"
      :on-change="uploadChangeFile"
      :disabled="isUploading"
      :auto-upload="false"
      :show-file-list="false"
      :file-list="fileList"
      drag
      multiple
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处，或<em>点击上传</em></div>
      <div class="el-upload__tip text-center" slot="tip">
        <div :style="'height: calc(70vh - 230px)'">
          <el-table
            :data="groupedFiles"
            row-key="uid"
            :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
            :row-class-name="tableRowClassName"
          >
            <el-table-column label="文件名称" align="left" prop="name" show-overflow-tooltip />
            <el-table-column label="文件类型" align="center" prop="type" width="80" show-overflow-tooltip />
            <el-table-column label="文件大小" align="center" prop="size" width="80" show-overflow-tooltip>
              <template v-slot="scope">
                <span>{{ $fileSize(scope.row.size) }}</span>
              </template>
            </el-table-column>
            <el-table-column label="状态" align="center" prop="status" width="250">
              <template v-slot="scope">
                <el-tag v-if="scope.row.status === '0'" type="success">读取完毕</el-tag>
                <el-tag v-if="scope.row.status === '1'" type="warning">压缩中，请稍后...</el-tag>
                <el-tag v-if="scope.row.status === '2'" type="warning">计算中，请稍后...</el-tag>
                <el-progress v-if="scope.row.status === '3'" :percentage="scope.row.progress" :color="customColors" />
              </template>
            </el-table-column>
            <el-table-column align="center" width="50">
              <template v-slot="scope">
                <el-button
                  size="mini"
                  type="text"
                  icon="el-icon-delete"
                  :disabled="deleteDisabled"
                  @click="handleDeleteFile(scope.$index, scope.row)"
                />
              </template>
            </el-table-column>
          </el-table>
        </div>
        <div class="upload-div">
          <el-tag>文件数量：{{ totalChildCount }}</el-tag>
          <el-tag type="warning">总大小：{{ $fileSize(fileTotalSize) }}</el-tag>
        </div>
        <div class="upload-start">
          <el-button type="primary" @click="handleFileUpload" :disabled="uploadDisabled">开始上传</el-button>
        </div>
      </div>
    </el-upload>
  </el-dialog>
</template>

<script>
import { MerkleTree } from '@/utils/MerkleTree';
import { getChunkUrl, mergeFile } from "@/api/tool/minio";
import axios from "axios";
import { addImgInfo, isExistFile } from "@/api/business/imgInfo";
import imageCompression from 'browser-image-compression';

export default {
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      uploadVisible: false,
      fileList: [],
      uploadDisabled: true,
      fileTotalSize: 0,
      groupedFiles: [],
      totalChildCount: 0,
      deleteDisabled: false,
      percentage: 0,
      isUploading: false,
      customColors: [
        { color: "#f56c6c", percentage: 20 },
        { color: "#e6a23c", percentage: 40 },
        { color: "#5cb87a", percentage: 60 },
        { color: "#1989fa", percentage: 80 },
        { color: "#6f7ad3", percentage: 100 },
      ],
      successfulGroupsCount: 0,
      successfulFilesCount: 0,
      skippedGroupsCount: 0,
      skippedFilesCount: 0,
    };
  },
  watch: {
    visible(val) {
      this.uploadVisible = val;
    },
  },
  methods: {
    /** 弹窗关闭 */
    closeDialog() {
      this.$emit("close");
      this.resetState();
    },
    /** 重置状态 */
    resetState() {
      this.fileList = [];
      this.fileTotalSize = 0;
      this.groupedFiles = [];
      this.uploadVisible = false;
      this.isUploading = false;
      this.successfulGroupsCount = 0;
      this.successfulFilesCount = 0;
      this.skippedGroupsCount = 0;
      this.skippedFilesCount = 0;
      this.totalChildCount = 0;
    },
    /** 文件选择钩子 */
    async uploadChangeFile(file) {
      if (!file || !file.raw || !(file.raw instanceof File)) {
        this.$modal.msgError("无效的文件信息，请重新选择文件。");
        return;
      }

      const validSize = Number(file.size);
      if (isNaN(validSize) || !isFinite(validSize)) {
        this.$modal.msgError("文件大小不合法，请确认后重试。");
        return;
      }

      this.fileTotalSize += validSize;
      this.addFileToGroup(file);
      this.updateFileCountAndUploadState();
    },
    /** 压缩图片 */
    async compressImage(file, maxSize) {
      const options = {
        maxSizeMB: maxSize / (1024 * 1024),
        useWebWorker: true,
        maxWidthOrHeight: 1920,
        fileType: "image/png",
      };

      try {
        return await imageCompression(file, options);
      } catch (error) {
        throw error;
      }
    },
    /** 文件分组 */
    addFileToGroup(file) {
      const fileName = file.name.split(".").slice(0, -1).join(".");
      const fileExt = file.name.split(".").pop();

      let group = this.groupedFiles.find((group) => group.name === fileName);
      if (!group) {
        group = { name: fileName, children: [], size: 0, uid: fileName, status: '0' };
        this.groupedFiles.push(group);
      }

      group.children.push({
        name: file.name,
        type: fileExt,
        size: file.size,
        hash: "",
        uid: file.uid,
        raw: file.raw,
        progress: 0,
        status: '0'
      });
      group.size += file.size;
    },
    /** 删除文件 */
    handleDeleteFile(index, row) {
      if (Array.isArray(row.children)) {
        row.children.forEach((child) => this.handleDeleteFile(index, child));
        row.children = [];
      }

      const fileIndex = this.fileList.findIndex((file) => file.uid === row.uid);
      if (fileIndex !== -1) {
        this.fileList.splice(fileIndex, 1);
      }

      if (!Array.isArray(row.children) || row.children.length === 0) {
        this.fileTotalSize -= row.size;
      }

      this.removeFileFromGroup(row);
      this.updateFileCountAndUploadState();
    },
    /** 从分组中删除文件 */
    removeFileFromGroup(row) {
      const groupIndex = this.groupedFiles.findIndex((group) =>
        group.children.some((child) => child.uid === row.uid)
      );
      if (groupIndex !== -1) {
        const group = this.groupedFiles[groupIndex];
        const childIndex = group.children.findIndex((child) => child.uid === row.uid);
        if (childIndex !== -1) {
          group.children.splice(childIndex, 1);
          group.size -= row.size;
        }
        if (group.children.length === 0) {
          this.groupedFiles.splice(groupIndex, 1);
        }
      }

      const topLevelGroupIndex = this.groupedFiles.findIndex((group) => group.uid === row.uid);
      if (topLevelGroupIndex !== -1) {
        this.groupedFiles.splice(topLevelGroupIndex, 1);
      }
    },
    /** 判断是否为有效的分组 */
    isGroupValid(children) {
      const sourceFiles = children.filter((child) =>
        ["psd", "tif", "cdr", "tiff", "ai", "psb"].includes(child.type.toLowerCase())
      );
      const jpgFiles = children.filter((child) => child.type.toLowerCase() === "jpg");
      const pngFiles = children.filter((child) => child.type.toLowerCase() === "png");
      const otherFiles = children.filter(
        (child) =>
          !["psd", "tif", "cdr", "jpg", "png", "tiff", "ai", "psb"].includes(child.type.toLowerCase())
      );

      return sourceFiles.length === 1 && jpgFiles.length === 1 && pngFiles.length === 1 && otherFiles.length === 0;
    },
    /** 更新文件数量和上传状态 */
    updateFileCountAndUploadState() {
      this.totalChildCount = this.groupedFiles.reduce((acc, group) => acc + group.children.length, 0);
      const allGroupsValid = this.groupedFiles.every((group) => this.isGroupValid(group.children));
      this.uploadDisabled = !(this.totalChildCount > 0 && allGroupsValid);
    },
    /** 文件上传操作 */
    async handleFileUpload() {
      let uploadingGroupsCount = 0;
      let uploadedGroupsCount = 0;
      let uploadedFilesCount = 0;
      let skippedGroupsCount = 0;
      let skippedFilesCount = 0;

      this.deleteDisabled = true;
      this.uploadDisabled = true;
      this.isUploading = true;

      // 遍历所有文件，对 PNG 图片进行压缩
      for (const group of this.groupedFiles) {
        for (const child of group.children) {
          if (child.raw.type === "image/png") {
            try {
              this.updateFileStatus(child.uid, "1");
              this.updateFileStatus(group.uid, "1");

              const childFileSize = child.size;
              const compressedFile = await this.compressImage(child.raw, 1024 * 1024);
              const compressedFileSize = compressedFile.size;

              child.raw = compressedFile;
              child.size = compressedFileSize;

              this.updateFileStatus(child.uid, "0");
              group.size += compressedFileSize - childFileSize;

              const isGroupCompressing = group.children.some(child => child.status === "1");
              if (!isGroupCompressing) {
                this.updateFileStatus(group.uid, "0");
              }
            } catch (error) {
              this.$modal.msgError("图片压缩失败，上传源文件。");
            }
          }
        }
      }

      this.fileTotalSize = this.groupedFiles.reduce((acc, group) => acc + group.size, 0);
      uploadingGroupsCount = this.groupedFiles.length;

      for (const group of this.groupedFiles) {
        this.processGroup(group).then(({ isUploaded, isSkipped }) => {
          uploadingGroupsCount--;

          if (isUploaded) {
            uploadedGroupsCount++;
            uploadedFilesCount += group.children.length;
          } else if (isSkipped) {
            skippedGroupsCount++;
            skippedFilesCount += group.children.length;
          }

          if (uploadingGroupsCount === 0) {
            this.closeDialog();
            this.$emit('refreshTable');
            this.$modal.msgSuccess(`上传成功：${uploadedGroupsCount}组，共${uploadedFilesCount}个文件。跳过：${skippedGroupsCount}组，共${skippedFilesCount}个文件。`);
          }
        });
      }
    },
    /** 处理文件组 */
    async processGroup(group) {
      const childFiles = group.children.map((child) => ({
        file: child.raw,
        uid: child.uid,
      }));

      this.updateFileStatus(group.uid, "2");

      const promises = childFiles.map(async ({ file, uid }) => {
        this.updateFileStatus(uid, "2");

        const { hash, chunks } = await this.getFileMd5(file);
        this.updateGroupedFiles(uid, hash, chunks);

        this.updateFileStatus(uid, "3");

        const isGroupCalculating = group.children.some(child => child.status === "2");
        if (!isGroupCalculating) {
          this.updateFileStatus(group.uid, "3");
        }

        return hash;
      });

      const hashes = await Promise.all(promises);

      for (const hash of hashes) {
        const fileExtension = group.children.find(child => child.hash === hash).type.toLowerCase();
        if (["psd", "tif", "cdr", "tiff", "ai", "psb"].includes(fileExtension)) {
          this.$set(group, "imgHash", hash);
          break;
        }
      }

      // if (group.imgHash) {
      //   const exists = await this.checkExistFile(group.imgHash);
      //   if (exists) {
      //     return { isUploaded: false, isSkipped: true };
      //   }
      // }

      await this.uploadGroup(group);
      return { isUploaded: true, isSkipped: false };
    },
    /** 上传文件组 */
    async uploadGroup(group) {
      const fileInfoDB = {
        imgName: "",
        imgSourcePath: "",
        imgJpgPath: "",
        imgPngPath: "",
        imgType: "",
        imgHash: "",
        imgSourceSize: 0,
        imgJpgSize: 0,
        imgPngSize: 0,
      };

      for (const file of group.children) {
        // 获取分片上传地址
        const res = await getChunkUrl(file.chunks.length, file.name, null, file.raw.type);
        const { parts, uploadId, filePath } = res.data;

        for (let i = 0; i < parts.length; i++) {
          // 上传文件
          await this.uploadFilePear(parts[i].uploadUrl, file.chunks[i], file.raw.type, parts[i].parkNum, parts.length, file.uid);
        }

        // 合并文件
        await this.completeChunkFile(file.chunks.length, file.name, file.raw.type, uploadId, file.hash, filePath);

        const fileExtension = file.name.split(".").pop().toLowerCase();
        if (["psd", "tif", "cdr", "tiff", "ai", "psb"].includes(fileExtension)) {
          Object.assign(fileInfoDB, {
            imgName: file.name,
            imgSourcePath: filePath,
            imgSourceSize: file.raw.size,
            imgType: this.getFileType(file.name),
            imgHash: file.hash,
          });
        } else if (fileExtension === "jpg") {
          fileInfoDB.imgJpgPath = filePath;
          fileInfoDB.imgJpgSize = file.raw.size;
        } else if (fileExtension === "png") {
          fileInfoDB.imgPngPath = filePath;
          fileInfoDB.imgPngSize = file.raw.size;
        }
      }

      await addImgInfo(fileInfoDB);
    },
    /** 判断文件是否已存在 */
    async checkExistFile(imgHash) {
      const res = await isExistFile(imgHash);
      return res.data;
    },
    /** 更新分组中的文件信息 */
    updateGroupedFiles(uid, hash, chunks) {
      const group = this.groupedFiles.find((group) => group.children.some((child) => child.uid === uid));
      if (group) {
        const child = group.children.find((child) => child.uid === uid);
        if (child) {
          this.$set(child, "hash", hash);
          this.$set(child, "chunks", chunks);
          this.$set(child, "progress", 0);
        }
      }
    },
    /** 计算文件哈希值 */
    async getFileMd5(file) {
      const chunks = await this.$cutFile(file);
      const merkleTree = new MerkleTree(chunks.map((chunk) => chunk.hash));
      const md5 = merkleTree.getRootHash();
      const chunkFileList = chunks.map((chunk) => chunk.blob);
      return { hash: md5, chunks: chunkFileList };
    },
    /** 分片上传文件 */
    async uploadFilePear(url, file, contentType, partNum, totalParts, uid) {
      try {
        await axios.put(url, file, { headers: { "Content-Type": contentType } });
        this.updateFileProgress(uid, partNum, totalParts);
      } catch (error) {
        this.handleError(error, "分片上传失败，请重试。");
      }
    },
    /** 合并分片文件 */
    async completeChunkFile(chunkNum, fileName, contentType, uploadId, fileMd5, filePath) {
      try {
        await mergeFile(chunkNum, fileName, contentType, uploadId, fileMd5, filePath);
      } catch (error) {
        this.handleError(error, "文件合并失败，请重试。");
      }
    },
    /** 更新文件上传进度 */
    updateFileProgress(uid, partNum, totalParts) {
      const group = this.groupedFiles.find((group) => group.children.some((child) => child.uid === uid));
      if (group) {
        const child = group.children.find((child) => child.uid === uid);
        if (child) {
          this.$set(child, "progress", Math.round((partNum / totalParts) * 100));

          const totalProgress = group.children.reduce((acc, child) => acc + child.progress, 0);
          const averageProgress = Math.round(totalProgress / group.children.length);
          this.$set(group, "progress", averageProgress);
        }
      }
    },
    /** 获取文件类型 */
    getFileType(fileName) {
      const ext = fileName.split(".").pop().toLowerCase();
      const typeMap = {
        psd: "1",
        tif: "2",
        tiff: "3",
        cdr: "4",
        ai: "5",
        psb: "6",
      };
      return typeMap[ext] || "";
    },
    /** 统一更新文件状态的方法 */
    updateFileStatus(uid, status, progress = 0) {
      const group = this.groupedFiles.find((group) => group.children.some((child) => child.uid === uid));
      if (group) {
        const child = group.children.find((child) => child.uid === uid);
        if (child) {
          this.$set(child, "status", status);
          this.$set(child, "progress", progress);

          // 检查父组件的状态是否需要更新
          this.updateParentStatus(group);
        }
      }
    },
    /** 更新父组件的状态 */
    updateParentStatus(group) {
      const childrenStatuses = group.children.map(child => child.status);

      if (childrenStatuses.includes("3")) {
        this.$set(group, "status", "3"); // 如果有子组件在上传中，父组件显示进度条
      } else if (childrenStatuses.includes("2")) {
        this.$set(group, "status", "2"); // 如果有子组件在计算中，父组件状态为“计算中”
      } else if (childrenStatuses.includes("1")) {
        this.$set(group, "status", "1"); // 如果有子组件在压缩中，父组件状态为“压缩中”
      } else {
        this.$set(group, "status", "0"); // 所有子组件都完成，父组件状态为“读取完毕”
      }
    },
    /** 设置表格行样式 */
    tableRowClassName({ row }) {
      if (row.children && !this.isGroupValid(row.children)) {
        return "warning-row";
      }
      return "";
    },
  },
};
</script>

<style scoped>
/deep/ .el-upload .el-upload-dragger {
  width: 920px;
  height: 150px;
}
/deep/ .el-dialog__body {
  height: 70vh !important;
}
.upload-div {
  float: left;
  text-align: left;
  display: flex;
  gap: 10px;
  padding-top: 15px;
}
.upload-start {
  float: right;
  padding-top: 10px;
  text-align: right;
}
::v-deep .el-table .warning-row td {
  background: #f56c6c !important;
}
</style>
